﻿//CandleManagerContainer.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Candles
{
	using System;
	using System.Collections.Generic;
	using System.Linq;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.ComponentModel;

	/// <summary>
	/// Стандартный контейнер, хранящий данные свечек.
	/// </summary>
	public class CandleManagerContainer : ICandleManagerContainer
	{
		private sealed class SeriesInfo
		{
			private readonly CandleManagerContainer _container;
			private const int _candlesCapacity = 10000;

			private readonly SynchronizedDictionary<DateTime, SynchronizedSet<Candle>> _byTime = new SynchronizedDictionary<DateTime, SynchronizedSet<Candle>>(_candlesCapacity);
			private readonly SynchronizedLinkedList<Candle> _allCandles = new SynchronizedLinkedList<Candle>();

			private DateTime _firstCandleTime;
			private DateTime _lastCandleTime;

			public SeriesInfo(CandleManagerContainer container)
			{
				if (container == null)
					throw new ArgumentNullException("container");

				_container = container;
			}

			public int CandleCount
			{
				get { return _allCandles.Count; }
			}

			public void Reset(DateTime from)
			{
				_firstCandleTime = from;

				_byTime.Clear();
				_allCandles.Clear();
			}

			public bool AddCandle(Candle candle)
			{
				if (candle == null)
					throw new ArgumentNullException("candle");

				if (_byTime.SafeAdd(candle.OpenTime).TryAdd(candle))
				{
					_allCandles.AddLast(candle);

					_lastCandleTime = candle.OpenTime;

					MemoryStatistics.Instance.Candles.Add(candle);
					RecycleCandles();

					return true;
				}

				return false;
			}

			public void RecycleCandles()
			{
				if (_container.CandlesKeepTime == TimeSpan.Zero)
					return;

				var diff = _lastCandleTime - _firstCandleTime;

				if (diff <= _container._maxCandlesKeepTime)
					return;

				_firstCandleTime = _lastCandleTime - _container.CandlesKeepTime;

				_allCandles.SyncDo(list =>
				{
					while (list.Count > 0)
					{
						if (list.First.Value.OpenTime >= _firstCandleTime)
							break;

						list.RemoveFirst();

						MemoryStatistics.Instance.Candles.Remove(list.First.Value);
					}
				});

				_byTime.SyncGet(d => d.RemoveWhere(p => p.Key < _firstCandleTime));
			}

			public IEnumerable<Candle> GetCandles(DateTime time)
			{
				var candles = _byTime.TryGetValue(time);

				return candles != null ? candles.SyncGet(c => c.ToArray()) : Enumerable.Empty<Candle>();
			}

			public IEnumerable<Candle> GetCandles()
			{
				return _allCandles.SyncGet(c => c.ToArray());
			}

			public Candle GetCandle(int candleIndex)
			{
				return _allCandles.SyncGet(c => c.ElementAtFromEndOrDefault(candleIndex));
			}
		}

		private readonly SynchronizedDictionary<CandleSeries, SeriesInfo> _candles = new SynchronizedDictionary<CandleSeries, SeriesInfo>();
		private TimeSpan _maxCandlesKeepTime;
		
		/// <summary>
		/// Создать <see cref="CandleManagerContainer"/>.
		/// </summary>
		public CandleManagerContainer()
		{
			CandlesKeepTime = TimeSpan.FromDays(2);
		}

		private TimeSpan _candlesKeepTime;

		/// <summary>
		/// Время хранения свечек в памяти. По-умолчанию равно 2-ум дням.
		/// </summary>
		/// <remarks>Если значение установлено в <see cref="TimeSpan.Zero"/>, то свечки не будут удаляться.</remarks>
		public TimeSpan CandlesKeepTime
		{
			get { return _candlesKeepTime; }
			set
			{
				if (value < TimeSpan.Zero)
					throw new ArgumentOutOfRangeException("value", value, "Время хранения свечек не может быть отрицательным.");

				_candlesKeepTime = value;
				_maxCandlesKeepTime = TimeSpan.FromTicks((long)(value.Ticks * 1.5));

				_candles.SyncDo(d => d.ForEach(p => p.Value.RecycleCandles()));
			}
		}

		/// <summary>
		/// Добавить свечку для серии.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <param name="candle">Свечка.</param>
		/// <returns>True, если свечка не ранее добавлена, иначе, false.</returns>
		public bool AddCandle(CandleSeries series, Candle candle)
		{
			var info = GetInfo(series);

			if (info == null)
				throw new InvalidOperationException("Серия {0} не была ранее запущена.".Put(series));

			return info.AddCandle(candle);
		}

		/// <summary>
		/// Получить для серии все ассоциированные с ней свечки на период <paramref name="time"/>.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <param name="time">Период свечки.</param>
		/// <returns>Свечки.</returns>
		public IEnumerable<Candle> GetCandles(CandleSeries series, DateTime time)
		{
			var info = GetInfo(series);
			return info != null ? info.GetCandles(time) : Enumerable.Empty<Candle>();
		}

		/// <summary>
		/// Получить для серии все ассоциированные с ней свечки.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <returns>Свечки.</returns>
		public IEnumerable<Candle> GetCandles(CandleSeries series)
		{
			var info = GetInfo(series);
			return info != null ? info.GetCandles() : Enumerable.Empty<Candle>();
		}

		/// <summary>
		/// Получить свечку по индексу.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <param name="candleIndex">Порядковый номер свечки с конца.</param>
		/// <returns>Найденая свечка. Если свечки не существует, то будет возвращено null.</returns>
		public Candle GetCandle(CandleSeries series, int candleIndex)
		{
			var info = GetInfo(series);
			return info != null ? info.GetCandle(candleIndex) : null;
		}

		/// <summary>
		/// Получить свечки по серии и диапазону дат.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <param name="timeRange">Диапазон дат, в которые должны входить свечки. Учитывается значение <see cref="Candle.OpenTime"/>.</param>
		/// <returns>Найденные свечки.</returns>
		public IEnumerable<Candle> GetCandles(CandleSeries series, Range<DateTime> timeRange)
		{
			return GetCandles(series)
						.Where(c => timeRange.Contains(c.OpenTime))
						.OrderBy(c => c.OpenTime);
		}

		/// <summary>
		/// Получить свечки по серии и общему количеству.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <param name="candleCount">Количество свечек, которое необходимо вернуть.</param>
		/// <returns>Найденные свечки.</returns>
		public IEnumerable<Candle> GetCandles(CandleSeries series, int candleCount)
		{
			if (candleCount < 0)
				throw new ArgumentOutOfRangeException("candleCount");

			return GetCandles(series)
							.OrderByDescending(c => c.OpenTime)
							.Take(candleCount)
							.OrderBy(c => c.OpenTime);
		}

		/// <summary>
		/// Получить количество свечек.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <returns>Количество свечек.</returns>
		public int GetCandleCount(CandleSeries series)
		{
			var info = GetInfo(series);
			return info == null ? 0 : info.CandleCount;
		}

		/// <summary>
		/// Известить контейнер для начале получения свечек для серии.
		/// </summary>
		/// <param name="series">Серия свечек.</param>
		/// <param name="from">Начальная дата, с которой будут получаться свечки.</param>
		/// <param name="to">Конечная дата, до которой будут получаться свечки.</param>
		public void Start(CandleSeries series, DateTime from, DateTime to)
		{
			if (series == null)
				throw new ArgumentNullException("series");

			var info = _candles.SafeAdd(series, key => new SeriesInfo(this));
			info.Reset(from);
		}

		private SeriesInfo GetInfo(CandleSeries series)
		{
			if (series == null)
				throw new ArgumentNullException("series");

			return _candles.TryGetValue(series);
		}
	}
}